Skip to content

feat(task-board): delivery lanes behind a default-off org flag - #6457

Merged
viktormarinho merged 1 commit into
mainfrom
iqaluit
Aug 27, 2026
Merged

viktormarinho merged 1 commit into
mainfrom
iqaluit

Conversation

@vibe-dex

@vibe-dex vibe-dex commented Aug 24, 2026 •

Copy link
Copy Markdown
Contributor

What is this contribution about?

Adds three board lanes — Approved, Merged, Post-deploy Validation — between In Review and Done, for teams whose release process continues after the merge, gated by a new default-off org flag delivery_lanes_enabled. Every automatic ship path (the review-decision auto-merge, the auto-merge retry, "Ship to production", prs-get's reconcile-on-view, and the merged sweep) now reads one function, shippedLane(flags), so "flag off = zero behaviour change" is a single tested fact rather than five call sites agreeing by luck.

Along the way this fixes a latent bug: prs-get's status !== "done" && !== "archived" enumeration would have dragged a card resting in Post-deploy Validation back to Merged on every PR-modal open, so it and run-reactions' delivered-failure relabel now compare by rank (LANE_RANK/movesForward, extracted into lanes.ts). Three related holes are closed in the same PR: REVIEW_CLOSING_STATUSES now covers the new lanes (a run setting the ship lane otherwise escaped closesOwnReview and dropped the card out of listItemsPendingReview, so its reviewers never ran), hasHumanRejectedDone treats leaving the ship lane as the same veto as leaving done, and the web ship-button gate accepts Approved so the lane isn't a UI dead end.

Note approved is deliberately human-only here — reachable by drag, "Move to" and Jira mapping, never written by automation. Making automation write it requires widening seven sites together (listItemsPendingReview, review-sweeper, advanceToDoneIfMerged, retryAutoMergeIfApproved, claimInReviewSuperAgentSlot, conflict-reaction, isTaskHandedToHuman); any subset strands cards, so that is a follow-up.

Adoption note — who wrote what

The branch is @fernandofrizzatti's and the authorship is preserved, but it sat behind 290 commits of main and I took it over to land it. The rebase had eight conflicted files and git folded my fixes into his single commit, so the diff does not separate his work from mine. Everything below the line is mine; everything above is his.

The rebase. #6544 landed in the middle of the drift and touched the same lines in reconcile-merged and prs-get. Both meanings are kept: cardWorkLanded decides whether a card's work landed (per repo, so a bounced PR doesn't strand it), shippedLane decides which lane it lands on. tool-io.ts was regenerated rather than hand-merged.

The e2e never passed. It asserted expect(shipError).not.toMatch(...) on a value that is null when the ship succeeds, and Bun throws on a non-string matcher argument — so it failed in exactly the case it was written to confirm. Now .then(() => "", (e) => String(e)).

The merged-tag sweep would have gone silent. It gated on status = 'done', but with the flag on a merged PR leaves the card on a delivery lane, so nothing would be tagged until a human finished dragging. TAGGABLE_MERGED_STATUSES (storage) and isTaggableMergedStatus (lanes.ts) now cover the lanes a merge can leave a card on. The archive sweep deliberately still gates on Done only, and the asymmetry is the point: a card in a delivery lane is still in flight, while the tag is a statement about the pull request.

deployed is renamed to merged (23 files + regenerated contracts). It's the signal Studio actually has — nothing reads a deployment; what moves the card is GitHub reporting the PR merged. The name is free to change today and becomes a data migration the moment any org enables the flag.

Two of the tests were written against the pre-#6544 reader shape (async () => true, [true]). Ported to { state, merged } / PrLanding rather than deleted.

How did you verify your code works?

New pure unit suites lanes.test.ts (rank ordering, movesForward refusing the backward drag, shippedLane resolving every falsy flag shape to done), plus new cases in config.test.ts (moveTargets, laneVisibility incl. the "card stuck in a switched-off lane stays reachable" property) and review-status.test.ts (laneCanShip). Per the repo's inversion rule the tests that encoded the old behaviour were inverted rather than appended to: reconcile-merged.test.ts now asserts both flag-off → done and flag-on → the ship lane, and update.test.ts's closesOwnReview "anywhere but Done/Archived" case now pins the delivery lanes as review-closing.

Real-Postgres coverage in archive-merged.integration.test.ts (a card parked in a delivery lane is never swept, via both the candidate query and the write path), human-rejected-done.integration.test.ts (leaving the ship lane is a veto; leaving a later lane is not), and two new cases for the widened tag sweep.

The e2e now runs and passes in CI — task-board-delivery-lanes.spec.ts, 3 tests, e2e-shard 2. That resolves the "unrun, worth a run before merge" caveat this description used to carry. Locally: bun run check 0 type errors, api suite green against real Postgres, bun run lint at baseline, knip clean, fmt:check clean. Every CI check on the branch is green.

Screenshots/Demonstration

Not captured. The UI change is three additional board columns plus one settings toggle, both invisible until the flag is enabled.

How to Test

  1. On the task board with the flag unset, confirm the board is unchanged from main (5 visible columns, Archived hidden) and that "Move to" and the task dialog's status dropdown offer no new lanes.
  2. Enable it: ORGANIZATION_SETTINGS_UPDATE { flags: { delivery_lanes_enabled: true } }, or Settings → Review → "Show delivery lanes".
  3. Drag a card In Review → Approved → Merged → Post-deploy Validation → Done; each move should persist and write a status_changed timeline entry.
  4. Confirm "Ship to production" is offered from Approved and not from Merged.
  5. Turn the flag back off with a card still in Merged — the lane must appear under "Hidden columns" with a count of 1, and "Show" must bring the column back so the card can be dragged out (it should also stay visible in list view throughout).
  6. Expected: with the flag off, a merged PR still lands the card on Done; with it on, the same merge lands it on Merged.

Migration Notes

None. task_board_items.status is plain text with no CHECK constraint (migration 126) and the existing status_changed activity action is reused, so no schema change is needed. The generated packages/shared/src/tools/tool-io.ts diff is committed — regenerate with bun run --cwd=apps/api generate:tool-contracts if you touch the status or flags schemas.

Review Checklist

  • PR title is clear and descriptive
  • Changes are tested and working
  • Documentation is updated (if needed)
  • No breaking changes

@github-actions github-actions Bot added the claude PR authored by a coding agent label Aug 24, 2026
Adds Approved, Merged and Post-deploy Validation between In Review and Done,
gated by a default-off org flag `delivery_lanes_enabled`. Every automatic ship
path reads one function, `shippedLane(flags)`, so "flag off = zero behaviour
change" is a single tested fact rather than five call sites agreeing by luck.

Rebased onto main after 290 commits, with four fixes folded in (see the PR
description for what changed and why they were not left as follow-ups):

The e2e asserted `expect(shipError).not.toMatch(...)` on a value that is `null`
when the ship SUCCEEDS, so it failed precisely in the case it was written to
confirm.

The merged-tag sweep would have gone silent. It gated on `status = 'done'`, and
with the lanes on a merged pull request lands the card on a delivery lane, so
nothing would be tagged until a human finished dragging it. The archive sweep
deliberately still gates on Done: a card in a delivery lane is in flight, while
the tag is a statement about the pull request.

`deployed` is renamed to `merged`, because that is the signal Studio has.
Nothing reads a deployment; what moves a card here is GitHub reporting the pull
request merged. Free today, a data migration the moment anyone enables the flag.

The rebase kept both sides where #6544 had landed in between: `cardWorkLanded`
decides WHETHER a card landed, `shippedLane` decides WHICH lane, and two tests
written against the older reader shape were ported rather than dropped.

Co-authored-by: Viktor Marinho <viktor@deco.cx>
@viktormarinho
viktormarinho merged commit 5ae23f1 into main Aug 27, 2026
34 checks passed
@viktormarinho
viktormarinho deleted the iqaluit branch August 27, 2026 13:58
decocms Bot pushed a commit that referenced this pull request Aug 27, 2026
PR: #6457 feat(task-board): delivery lanes behind a default-off org flag
Bump type: minor

- decocms (apps/api/package.json): 4.280.3 -> 4.281.0
- @decocms/native (apps/native/package.json): 4.280.3 -> 4.281.0
- @decocms/e2e (packages/e2e/package.json): 1.51.0 -> 1.52.0
- @decocms/shared (packages/shared/package.json): 0.60.1 -> 0.61.0

Deploy-Scope: both
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

claude PR authored by a coding agent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants